/*
 * Created on 08-Jun-2004
 * Created by Paul Gardner
 * Copyright (C) Azureus Software, Inc, All Rights Reserved.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 */

package org.bouncycastle.jce.provider;

/**
 * @author parg
 *
 */

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.KeyFactorySpi;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.interfaces.DSAPrivateKey;
import java.security.interfaces.DSAPublicKey;
import java.security.interfaces.RSAPrivateCrtKey;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.RSAPrivateCrtKeySpec;
import java.security.spec.RSAPrivateKeySpec;
import java.security.spec.RSAPublicKeySpec;
import java.security.spec.X509EncodedKeySpec;

import javax.crypto.interfaces.DHPrivateKey;
import javax.crypto.interfaces.DHPublicKey;

import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.DERInputStream;
import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
import org.bouncycastle.asn1.x509.X509ObjectIdentifiers;
import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
import org.bouncycastle.jce.interfaces.ElGamalPrivateKey;
import org.bouncycastle.jce.interfaces.ElGamalPublicKey;
import org.bouncycastle.jce.spec.ECPrivateKeySpec;
import org.bouncycastle.jce.spec.ECPublicKeySpec;
import org.gudy.azureus2.core3.util.Debug;

public abstract class JDKKeyFactory extends KeyFactorySpi {
    protected KeySpec engineGetKeySpec(Key key, Class spec) throws InvalidKeySpecException {
        if (spec.isAssignableFrom(PKCS8EncodedKeySpec.class) && key.getFormat().equals("PKCS#8")) {
            return new PKCS8EncodedKeySpec(key.getEncoded());
        } else if (spec.isAssignableFrom(X509EncodedKeySpec.class) && key.getFormat().equals("X.509")) {
            return new X509EncodedKeySpec(key.getEncoded());
        } else if (spec.isAssignableFrom(RSAPublicKeySpec.class) && key instanceof RSAPublicKey) {
            RSAPublicKey k = (RSAPublicKey) key;

            return new RSAPublicKeySpec(k.getModulus(), k.getPublicExponent());
        } else if (spec.isAssignableFrom(RSAPrivateKeySpec.class) && key instanceof RSAPrivateKey) {
            RSAPrivateKey k = (RSAPrivateKey) key;

            return new RSAPrivateKeySpec(k.getModulus(), k.getPrivateExponent());
        } else if (spec.isAssignableFrom(RSAPrivateCrtKeySpec.class) && key instanceof RSAPrivateCrtKey) {
            RSAPrivateCrtKey k = (RSAPrivateCrtKey) key;

            return new RSAPrivateCrtKeySpec(k.getModulus(), k.getPublicExponent(), k.getPrivateExponent(), k.getPrimeP(), k.getPrimeQ(), k
                    .getPrimeExponentP(), k.getPrimeExponentQ(), k.getCrtCoefficient());
        }

        throw new RuntimeException("not implemented yet " + key + " " + spec);
    }

    protected Key engineTranslateKey(Key key) throws InvalidKeyException {
        if (key instanceof RSAPublicKey) {
            return new JCERSAPublicKey((RSAPublicKey) key);
        } else if (key instanceof RSAPrivateCrtKey) {
            // return new JCERSAPrivateCrtKey((RSAPrivateCrtKey)key);
        } else if (key instanceof RSAPrivateKey) {
            // return new JCERSAPrivateKey((RSAPrivateKey)key);
        } else if (key instanceof DHPublicKey) {
            // return new JCEDHPublicKey((DHPublicKey)key);
        } else if (key instanceof DHPrivateKey) {
            // return new JCEDHPrivateKey((DHPrivateKey)key);
        } else if (key instanceof DSAPublicKey) {
            // return new JDKDSAPublicKey((DSAPublicKey)key);
        } else if (key instanceof DSAPrivateKey) {
            // return new JDKDSAPrivateKey((DSAPrivateKey)key);
        } else if (key instanceof ElGamalPublicKey) {
            // return new JCEElGamalPublicKey((ElGamalPublicKey)key);
        } else if (key instanceof ElGamalPrivateKey) {
            // return new JCEElGamalPrivateKey((ElGamalPrivateKey)key);
        }

        throw new InvalidKeyException("key type unknown");
    }

    static PublicKey createPublicKeyFromDERStream(InputStream in) throws IOException {
        return createPublicKeyFromPublicKeyInfo(new SubjectPublicKeyInfo((ASN1Sequence) (new DERInputStream(in).readObject())));
    }

    static PublicKey createPublicKeyFromPublicKeyInfo(SubjectPublicKeyInfo info) {
        AlgorithmIdentifier algId = info.getAlgorithmId();

        if (algId.getObjectId().equals(PKCSObjectIdentifiers.rsaEncryption) || algId.getObjectId().equals(X509ObjectIdentifiers.id_ea_rsa)) {
            return new JCERSAPublicKey(info);
        } else if (algId.getObjectId().equals(X9ObjectIdentifiers.id_ecPublicKey)) {
            return new JCEECPublicKey(info);
        } else {
            throw new RuntimeException("algorithm identifier in key not recognised");
        }
    }

    static PrivateKey createPrivateKeyFromDERStream(InputStream in) throws IOException {
        return createPrivateKeyFromPrivateKeyInfo(new PrivateKeyInfo((ASN1Sequence) (new DERInputStream(in).readObject())));
    }

    /**
     * create a private key from the given public key info object.
     */
    static PrivateKey createPrivateKeyFromPrivateKeyInfo(PrivateKeyInfo info) {
        AlgorithmIdentifier algId = info.getAlgorithmId();

        /*
         * if (algId.getObjectId().equals(PKCSObjectIdentifiers.rsaEncryption)) { return new JCERSAPrivateCrtKey(info); } else if
         * (algId.getObjectId().equals(PKCSObjectIdentifiers.dhKeyAgreement)) { return new JCEDHPrivateKey(info); } else if
         * (algId.getObjectId().equals(OIWObjectIdentifiers.elGamalAlgorithm)) { return new JCEElGamalPrivateKey(info); } else if
         * (algId.getObjectId().equals(X9ObjectIdentifiers.id_dsa)) { return new JDKDSAPrivateKey(info); } else
         */
        if (algId.getObjectId().equals(X9ObjectIdentifiers.id_ecPublicKey)) {
            return new JCEECPrivateKey(info);
        } else {
            throw new RuntimeException("algorithm identifier in key not recognised");
        }
    }

    public static class EC extends JDKKeyFactory {
        String algorithm;

        public EC()

        throws NoSuchAlgorithmException {
            this("EC");

            // PARG - bail if we're constructing an X509 cert for SSL as the BC SSL impl is old and doesn't have recent named curves
            // If we allow this to continue it borks constructing the EC public key and takes the whole SSL process down with
            // utimately a
            // Caused by: java.io.IOException: subject key, java.lang.NullPointerException
            // at sun.security.x509.X509Key.parse(X509Key.java:157)
            // at sun.security.x509.CertificateX509Key.<init>(CertificateX509Key.java:58)
            // at sun.security.x509.X509CertInfo.parse(X509CertInfo.java:688)
            // at sun.security.x509.X509CertInfo.<init>(X509CertInfo.java:152)

            try {
                StackTraceElement[] elements = new Exception().getStackTrace();

                boolean ssl = false;
                boolean x509 = false;

                for (StackTraceElement elt : elements) {

                    String name = elt.getClassName() + "." + elt.getMethodName();

                    if (name.contains("SSLSocketFactory") || name.contains("KeyStore.load")) {

                        ssl = true;
                    } else if (name.contains("X509")) {

                        x509 = true;
                    }
                }

                if (ssl && x509) {

                    // Debug.out( "Hacking SSL EC" );

                    throw (new NoSuchAlgorithmException());
                }
            } catch (NoSuchAlgorithmException e) {

                throw (e);

            } catch (Throwable e) {

                Debug.out(e);
            }
        }

        public EC(String algorithm) {
            this.algorithm = algorithm;
        }

        protected PrivateKey engineGeneratePrivate(KeySpec keySpec) throws InvalidKeySpecException {
            if (keySpec instanceof PKCS8EncodedKeySpec) {
                try {
                    return JDKKeyFactory.createPrivateKeyFromDERStream(new ByteArrayInputStream(((PKCS8EncodedKeySpec) keySpec).getEncoded()));
                } catch (Exception e) {
                    throw new InvalidKeySpecException(e.toString());
                }
            } else if (keySpec instanceof ECPrivateKeySpec) {
                return new JCEECPrivateKey(algorithm, (ECPrivateKeySpec) keySpec);
            }

            throw new InvalidKeySpecException("Unknown KeySpec type.");
        }

        protected PublicKey engineGeneratePublic(KeySpec keySpec) throws InvalidKeySpecException {
            if (keySpec instanceof X509EncodedKeySpec) {
                try {
                    return JDKKeyFactory.createPublicKeyFromDERStream(new ByteArrayInputStream(((X509EncodedKeySpec) keySpec).getEncoded()));
                } catch (Exception e) {
                    throw new InvalidKeySpecException(e.toString());
                }
            } else if (keySpec instanceof ECPublicKeySpec) {
                return new JCEECPublicKey(algorithm, (ECPublicKeySpec) keySpec);
            }

            throw new InvalidKeySpecException("Unknown KeySpec type.");
        }
    }

    public static class ECDSA extends EC {
        public ECDSA() {
            super("ECDSA");
        }
    }
}
